﻿using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using XNADota.Primitive;
using XNADota.Graphics;
using System.Diagnostics;
using XNADota.MDX;
using XNADota.GamePlay;

namespace XNADota.Terrain
{
    public class LandscapeRenderer
    {
        private GraphicsDevice _GraphicsDevice;
        private Landscape _Landscape;

        private List<LandscapePatchRenderer> _LandscapePatchRenderers;
        private List<PaletteInstance> _PaletteInstances;

        public LandscapeRenderer(Game game, GraphicsDevice GraphicsDevice, Landscape Landscape)
        {
            // assgin datas
            _GraphicsDevice = GraphicsDevice;
            _Landscape = Landscape;

            #region WarCraftEnvironment Including terrain surfaces and cliffs/clifftrans/ramps
            // Generate landscape cell
            _LandscapePatchRenderers = new List<LandscapePatchRenderer>();
            _LandscapePatchRenderers.Clear();

            // Recalculate heights for ramps
            CalculateRampsHeight();
            GenerateCliffs(game, _Landscape.WarCraftEnvironment.Width - 1, _Landscape.WarCraftEnvironment.Height - 1);

            // Generate each Resolution * Resolution terrain cell
            int Resolution = LandscapePatchRenderer.DefaultResolution;
            VertexPositionNormalTangentBinormalTexture[] container = new VertexPositionNormalTangentBinormalTexture[(Resolution + 1) * (Resolution + 1)];
            VertexInfo[,] VertexInfos = new VertexInfo[(Resolution + 1), (Resolution + 1)];
            for (int x = 0; x < _Landscape.WarCraftEnvironment.Width - 1; x += Resolution)
                for (int y = 0; y < _Landscape.WarCraftEnvironment.Height - 1; y += Resolution)
                {
                    for (int i = 0; i < Resolution + 1; i++)
                        for (int j = 0; j < Resolution + 1; j++)
                        {
                            int index = i + j * (Resolution + 1);
                            container[index] = GenerateTerrainVertex(x + i, y + j);
                            container[index].TextureCoordinate = new Vector2((float)i / (float)(Resolution), (float)j / (float)(Resolution));
                            VertexInfos[i, j] = _Landscape.WarCraftEnvironment.VertexInfos[Index(x + i, y + j)];

                            // push height data
                            GameplayWorld.Instance.PathManager.PushHeightData(container[index].Position, false);
                        }

                    LandscapePatchRenderer renderer = new LandscapePatchRenderer(game, GraphicsDevice, _Landscape, Resolution, container, VertexInfos);
                    _LandscapePatchRenderers.Add(renderer);

                    LandscapeCellInstance Instance = new LandscapeCellInstance(renderer);
                    SceneManager.Instance.AddInstance(Instance);
                }
            #endregion // WarCraftEnvironment

            #region WarCraftMapDoo Doodads includes trees and special doodads
            PaletteRendererManager manager = SceneManager.Instance.PaletteRendererManager;
            for (int i = 0; i < _Landscape.WarCraftMapDoo.TreeCount; i++)
            {
                var Tree = _Landscape.WarCraftMapDoo.Trees[i];
                Texture2D texture;

                Vector3 Position = new Vector3(Tree.Position.Y - Landscape.WarCraftEnvironment.CenterOffsetY,
                               Tree.Position.Z,
                               Tree.Position.X - Landscape.WarCraftEnvironment.CenterOffsetX);

                string TreeName = DoodadPaletteRendererContainer.GetName(Tree, Position, out texture);

                if (TreeName != null)
                {
                    Vector3 Rotation = new Vector3(Tree.RotationAngle - (float)Math.PI / 2, -(float)Math.PI / 2.0f, 0);
                    DoodadInstance Instance = new DoodadInstance(Position, Tree.Scale, Rotation,
                        ERenderState.Opaque | ERenderState.EnableDepth | ERenderState.CullCounterClockWise | ERenderState.Solid,
                        manager.GetPaletteRenderer(EPaletteRendererType.Doodad, TreeName) as DoodadRenderer);
                    Instance.ReplacableTexture = texture;
                    SceneManager.Instance.AddInstance(Instance);
                    SceneManager.Instance.AddUpdateInstance(Instance);
                }
            }

            //DoodadPaletteRendererContainer.PrintDebugRecords();
            #endregion // WarCraftMapDoo

            #region WarCraftUnitsDoodads, like maps
            manager = SceneManager.Instance.PaletteRendererManager;
            for (int i = 0; i < _Landscape.WarCraftUnitsDoo.UnitCount; i++)
            {
                var unit = _Landscape.WarCraftUnitsDoo.Units[i];

                if (unit.TypeId != null)
                {
                    Vector3 Position = new Vector3(unit.Position.Y - Landscape.WarCraftEnvironment.CenterOffsetY,
                                                   unit.Position.Z,
                                                   unit.Position.X - Landscape.WarCraftEnvironment.CenterOffsetX);
                    Vector3 Rotation = new Vector3(unit.Rotation.Y - (float)Math.PI / 2, -(float)Math.PI / 2.0f, 0);

                    string scale = GameplayWorld.Instance.ObjectsManager.GetFiledValue(EObjectType.UnitData, unit.TypeId, "modelScale");
                    if (scale != null) unit.Scale = unit.Scale * float.Parse(scale);

                    if (manager.GetPaletteRenderer(EPaletteRendererType.Unit, unit.TypeId) != null)
                    {
                        UnitObject Instance = new UnitObject(unit.TypeId);
                        Instance.Position = Position;
                        Instance.Scale = unit.Scale;
                        Instance.Orientation = Matrix.CreateFromYawPitchRoll(Rotation.X, Rotation.Y, Rotation.Z);
                        Instance.RenderStates = ERenderState.Opaque | ERenderState.EnableDepth | ERenderState.CullCounterClockWise | ERenderState.Solid;
                        Instance.ApplyPosition();

                        Instance.ReplacableTexture = ReplaceableTextures.Red;

                        SceneManager.Instance.AddInstance(Instance);
                        SceneManager.Instance.AddUpdateInstance(Instance);
                    }
                }
            }

            UnitPaletteRenderContainer.PrintDebugRecords();
            #endregion // WarCraftUnitsDoodads
        }

        private void GenerateCliffs(Game Game, int width, int height)
        {
            VertexInfo[,] VertexInfos = new VertexInfo[width + 1, height + 1];
            for (int i = 0; i < width + 1; i++)
                for (int j = 0; j < height + 1; j++)
                {
                    VertexInfos[i, j] = _Landscape.WarCraftEnvironment.VertexInfos[Index(i, j)];

                }

            Debug.Assert(VertexInfos.GetLength(0) == width + 1);
            Debug.Assert(VertexInfos.GetLength(1) == height + 1);

            _PaletteInstances = new List<PaletteInstance>();

            // 1-->3
            // |   |
            // 0-->2
            // 0 -> 1 -> 3, 0 -> 3 -> 2
            List<int> Triangles = new List<int>();
            for (int i = 0; i < width; i++)
                for (int j = 0; j < height; j++)
                {
                    string name;
                    ECellType cellType = CliffPaletteRendererContainer.GetCellType(
                        VertexInfos[i, j],
                        VertexInfos[i + 1, j],
                        VertexInfos[i + 1, j + 1],
                        VertexInfos[i, j + 1],
                        out name);

                    PaletteRendererManager manager = SceneManager.Instance.PaletteRendererManager;
                    if (cellType == ECellType.Cliff)
                    {
                        // position
                        Vector3 position = new Vector3(VertexInfos[i, j + 1].Position.X, 0, VertexInfos[i, j + 1].Position.Z);
                        position.Y = Math.Min(Math.Min(VertexInfos[i, j].Position.Y, VertexInfos[i + 1, j].Position.Y),
                                              Math.Min(VertexInfos[i, j + 1].Position.Y, VertexInfos[i + 1, j + 1].Position.Y));

                        // rotation & states
                        Vector3 rotation = new Vector3(0, -(float)Math.PI / 2.0f, -(float)Math.PI / 2.0f);
                        ERenderState states = ERenderState.Opaque | ERenderState.Solid | ERenderState.CullCounterClockWise | ERenderState.EnableDepth;

                        Texture2D cliffTexture = (VertexInfos[i, j].CliffTextureType == 1) ? ReplaceableTextures.Cliff0 : ReplaceableTextures.Cliff1;
                        int textureId = (VertexInfos[i, j].CliffTextureType == 1) ? 0 : 3;
                        PaletteInstance instance = new CliffInstance(VertexInfos[i, j].Position, VertexInfos[i + 1, j].Position,
                                                                     VertexInfos[i + 1, j + 1].Position, VertexInfos[i, j + 1].Position,
                                                                     new Vector3(), new Vector3(), false, EDirection.None, ESide.None,
                                                                     cliffTexture, position, rotation, states, manager.GetPaletteRenderer(EPaletteRendererType.Cliff, name) as CliffRenderer);
                        _PaletteInstances.Add(instance);
                        // Cliff changes tile texture around
                        VertexInfos[i, j].TextureId = VertexInfos[i + 1, j].TextureId = VertexInfos[i, j + 1].TextureId = VertexInfos[i + 1, j + 1].TextureId = (byte)textureId;
                    }

                    if (cellType == ECellType.CliffTrans)
                    {
                        const int DefaultGridSize = 128;

                        ESide side = ESide.None;

                        // it's cliff ramp, so it must have a direction, from higher layer to lower layer, and a side, left or right
                        EDirection direction = CliffPaletteRendererContainer.GetDirection(
                            VertexInfos[i, j].LayerHeight, VertexInfos[i, j].IsRampPoint,
                            VertexInfos[i + 1, j].LayerHeight, VertexInfos[i + 1, j].IsRampPoint,
                            VertexInfos[i + 1, j + 1].LayerHeight, VertexInfos[i + 1, j + 1].IsRampPoint,
                            VertexInfos[i, j + 1].LayerHeight, VertexInfos[i, j + 1].IsRampPoint, ref side);


                        // Set surface invisible
                        VertexInfos[i, j].SurfaceVisible[0] = false;
                        VertexInfos[i + 1, j].SurfaceVisible[1] = false;
                        VertexInfos[i + 1, j + 1].SurfaceVisible[2] = false;
                        VertexInfos[i, j + 1].SurfaceVisible[3] = false;

                        //DebugPrimitiveRenderer.AddPermanentPrimitive(VertexInfos[i, j].Position, Color.Red);
                        //DebugPrimitiveRenderer.AddPermanentPrimitive(VertexInfos[i + 1, j].Position, Color.Red);
                        //DebugPrimitiveRenderer.AddPermanentPrimitive(VertexInfos[i, j + 1].Position, Color.Red);
                        //DebugPrimitiveRenderer.AddPermanentPrimitive(VertexInfos[i + 1, j + 1].Position, Color.Red);

                        switch (direction)
                        {
                            case EDirection.North:
                                VertexInfos[i + 1, j].SurfaceVisible[0] = false;
                                VertexInfos[i + 2, j].SurfaceVisible[1] = false;
                                VertexInfos[i + 2, j + 1].SurfaceVisible[2] = false;
                                VertexInfos[i + 1, j + 1].SurfaceVisible[3] = false;
                                //DebugPrimitiveRenderer.AddPermanentPrimitive(VertexInfos[i + 2, j].Position, Color.Purple);
                                //DebugPrimitiveRenderer.AddPermanentPrimitive(VertexInfos[i + 2, j + 1].Position, Color.Purple);
                                break;
                            case EDirection.South:
                                VertexInfos[i - 1, j].SurfaceVisible[0] = false;
                                VertexInfos[i, j].SurfaceVisible[1] = false;
                                VertexInfos[i, j + 1].SurfaceVisible[2] = false;
                                VertexInfos[i - 1, j + 1].SurfaceVisible[3] = false;
                                //DebugPrimitiveRenderer.AddPermanentPrimitive(VertexInfos[i - 1, j].Position, Color.Purple);
                                //DebugPrimitiveRenderer.AddPermanentPrimitive(VertexInfos[i - 1, j + 1].Position, Color.Purple);
                                break;
                            case EDirection.West:
                                VertexInfos[i, j - 1].SurfaceVisible[0] = false;
                                VertexInfos[i + 1, j - 1].SurfaceVisible[1] = false;
                                VertexInfos[i + 1, j].SurfaceVisible[2] = false;
                                VertexInfos[i, j].SurfaceVisible[3] = false;
                                //DebugPrimitiveRenderer.AddPermanentPrimitive(VertexInfos[i + 1, j - 1].Position, Color.Purple);
                                //DebugPrimitiveRenderer.AddPermanentPrimitive(VertexInfos[i, j - 1].Position, Color.Purple);
                                break;
                            case EDirection.East:
                                VertexInfos[i, j + 1].SurfaceVisible[0] = false;
                                VertexInfos[i + 1, j + 1].SurfaceVisible[1] = false;
                                VertexInfos[i + 1, j + 2].SurfaceVisible[2] = false;
                                VertexInfos[i, j + 2].SurfaceVisible[3] = false;
                                //DebugPrimitiveRenderer.AddPermanentPrimitive(VertexInfos[i + 1, j + 2].Position, Color.Purple);
                                //DebugPrimitiveRenderer.AddPermanentPrimitive(VertexInfos[i, j + 2].Position, Color.Purple);
                                break;
                        }


                        Vector3 LeftBottom = VertexInfos[i, j].Position;
                        Vector3 LeftTop = VertexInfos[i + 1, j].Position;
                        Vector3 RightTop = VertexInfos[i + 1, j + 1].Position;
                        Vector3 RightBottom = VertexInfos[i, j + 1].Position;

                        Vector3 NoMove = new Vector3(0, 0, 0);
                        Vector3 MoveUp = new Vector3(DefaultGridSize, 0, 0);
                        Vector3 MoveDown = new Vector3(-DefaultGridSize, 0, 0);
                        Vector3 MoveLeft = new Vector3(0, 0, -DefaultGridSize);
                        Vector3 MoveRight = new Vector3(0, 0, DefaultGridSize);

                        Vector3 LeftMiddle = new Vector3();
                        Vector3 RightMiddle = new Vector3();

                        // Position
                        Vector3 position = new Vector3(VertexInfos[i, j + 1].Position.X, 0, VertexInfos[i, j + 1].Position.Z);
                        switch (direction)
                        {
                            case EDirection.North:
                                position += NoMove;
                                if (i + 2 <= width) LeftTop = VertexInfos[i + 2, j].Position; else Debug.Assert(false);
                                if (i + 2 <= width) RightTop = VertexInfos[i + 2, j + 1].Position; else Debug.Assert(false);
                                LeftMiddle = VertexInfos[i + 1, j].Position;
                                RightMiddle = VertexInfos[i + 1, j + 1].Position;
                                break;
                            case EDirection.South:
                                position += MoveDown;
                                if (i - 1 >= 0) LeftBottom = VertexInfos[i - 1, j].Position; else Debug.Assert(false);
                                if (i - 1 >= 0) RightBottom = VertexInfos[i - 1, j + 1].Position; else Debug.Assert(false);
                                LeftMiddle = VertexInfos[i, j].Position;
                                RightMiddle = VertexInfos[i, j + 1].Position;
                                break;
                            case EDirection.West:
                                position += NoMove;
                                if (j - 1 >= 0) LeftTop = VertexInfos[i + 1, j - 1].Position; else Debug.Assert(false);
                                if (j - 1 >= 0) LeftBottom = VertexInfos[i, j - 1].Position; else Debug.Assert(false);
                                LeftMiddle = VertexInfos[i + 1, j].Position;
                                RightMiddle = VertexInfos[i, j].Position;
                                break;
                            case EDirection.East:
                                position += MoveRight;
                                if (j + 2 <= width) RightTop = VertexInfos[i + 1, j + 2].Position; else Debug.Assert(false);
                                if (j + 2 <= width) RightBottom = VertexInfos[i, j + 2].Position; else Debug.Assert(false);
                                LeftMiddle = VertexInfos[i + 1, j + 1].Position;
                                RightMiddle = VertexInfos[i, j + 1].Position;
                                break;
                        }

                        position.Y = Math.Min(Math.Min(VertexInfos[i, j].Position.Y, VertexInfos[i + 1, j].Position.Y),
                                              Math.Min(VertexInfos[i, j + 1].Position.Y, VertexInfos[i + 1, j + 1].Position.Y));

                        // rotation & states
                        Vector3 rotation = new Vector3(0, -(float)Math.PI / 2.0f, -(float)Math.PI / 2.0f);
                        ERenderState states = ERenderState.Opaque | ERenderState.Solid | ERenderState.CullCounterClockWise | ERenderState.EnableDepth;

                        Texture2D cliffTexture = (VertexInfos[i, j].CliffTextureType == 1) ? ReplaceableTextures.Cliff0 : ReplaceableTextures.Cliff1;
                        int textureId = (VertexInfos[i, j].CliffTextureType == 1) ? 0 : 3;
                        //PaletteInstance instance = new PaletteInstance(

                        //                                              position, rotation, states, manager.GetPaletteRenderer(EPaletteRendererType.Cliff, name));
                        PaletteInstance instance = new CliffInstance(LeftBottom, LeftTop, RightTop, RightBottom, LeftMiddle, RightMiddle, true, direction, side,
                                      cliffTexture, position, rotation, states, manager.GetPaletteRenderer(EPaletteRendererType.Cliff, name) as CliffRenderer);

                        _PaletteInstances.Add(instance);
                        // Cliff changes tile texture around
                        VertexInfos[i, j].TextureId = VertexInfos[i + 1, j].TextureId =
                        VertexInfos[i, j + 1].TextureId = VertexInfos[i + 1, j + 1].TextureId = (byte)textureId;

                        switch (direction)
                        {
                            case EDirection.North:
                                VertexInfos[i + 2, j].TextureId = VertexInfos[i + 2, j + 1].TextureId = (byte)textureId;
                                break;
                            case EDirection.South:
                                VertexInfos[i - 1, j].TextureId = VertexInfos[i - 1, j + 1].TextureId = (byte)textureId;
                                break;
                            case EDirection.West:
                                VertexInfos[i, j - 1].TextureId = VertexInfos[i + 1, j - 1].TextureId = (byte)textureId;
                                break;
                            case EDirection.East:
                                VertexInfos[i + 1, j + 2].TextureId = VertexInfos[i + 1, j + 2].TextureId = (byte)textureId;
                                break;
                        }

                        // Debug information
                        //DebugPrimitiveRenderer.AddPermanentPrimitive(VertexInfos[i, j].Position, Color.Red);
                        //DebugPrimitiveRenderer.AddPermanentPrimitive(VertexInfos[i + 1, j].Position, Color.Green);
                        //DebugPrimitiveRenderer.AddPermanentPrimitive(VertexInfos[i, j + 1].Position, Color.Blue);
                    }
                }

            foreach (PaletteInstance instance in _PaletteInstances)
            {
                SceneManager.Instance.AddInstance(instance);
            }
        }

        private void CalculateRampsHeight()
        {
            const int IterationTimes = 5;
            for (int times = 0; times < IterationTimes; times++)
            {
                for (int x = 1; x < _Landscape.WarCraftEnvironment.Width - 1; x++)
                    for (int y = 1; y < _Landscape.WarCraftEnvironment.Height - 1; y++)
                        if (_Landscape.WarCraftEnvironment.VertexInfos[Index(x, y)].IsRampPoint)
                        {
                            float allX, allY, countX, countY;
                            allX = countX = 0;
                            // X direction
                            for (int dx = -1; dx < 2; dx++)
                                if ((dx != 0 && _Landscape.WarCraftEnvironment.VertexInfos[Index(x + dx, y)].IsRampPoint))
                                {
                                    allX += _Landscape.WarCraftEnvironment.VertexInfos[Index(x + dx, y)].Position.Y;
                                    countX++;
                                }

                            allY = countY = 0;
                            // Y direction
                            for (int dy = -1; dy < 2; dy++)
                                if ((dy != 0 && _Landscape.WarCraftEnvironment.VertexInfos[Index(x, y + dy)].IsRampPoint))
                                {
                                    allY += _Landscape.WarCraftEnvironment.VertexInfos[Index(x, y + dy)].Position.Y;
                                    countY++;
                                }

                            if (countX + countY == 4)
                                _Landscape.WarCraftEnvironment.VertexInfos[Index(x, y)].Position.Y = (allX + allY) / 4.0f;
                            else
                            {
                                if (countX == 2)
                                    _Landscape.WarCraftEnvironment.VertexInfos[Index(x, y)].Position.Y = allX / 2;
                                if (countY == 2)
                                    _Landscape.WarCraftEnvironment.VertexInfos[Index(x, y)].Position.Y = allY / 2;
                            }
                        }
            }
        }

        private int Index(int x, int y)
        {
            return x + y * (_Landscape.WarCraftEnvironment.Width);
        }

        private VertexPositionNormalTangentBinormalTexture GenerateTerrainVertex(int x, int y)
        {
            VertexPositionNormalTangentBinormalTexture terrainVertex = new VertexPositionNormalTangentBinormalTexture();

            // get position
            terrainVertex.Position = _Landscape.WarCraftEnvironment.VertexInfos[Index(x, y)].Position;
            //if (_Landscape.WarCraftEnvironment.VertexInfos[Index(x, y)].IsRampPoint)
            //    DebugPrimitiveRenderer.AddPermanentPrimitive(terrainVertex.Position + new Vector3(0, 0, 0), Color.Yellow);

            // calculate normal
            // shape
            // 6-->7-->8               7 is (x - 1, y)
            // |   |   |
            // 3-->4-->5               4 is (x, y)
            // |   |   |
            // 0-->1-->2               1 is (x - 1, y)
            Vector3[] position = new Vector3[9];
            bool[] valid = new bool[9];
            for (int i = -1; i <= 1; i++)
                for (int j = -1; j <= 1; j++)
                {
                    int index = (j + 1) + (i + 1) * 3;
                    if ((0 <= x + i) && (x + i <= _Landscape.WarCraftEnvironment.Width - 1) && (0 <= y + j) && (y + j <= _Landscape.WarCraftEnvironment.Height - 1))
                    {
                        valid[index] = true;
                        position[index] = _Landscape.WarCraftEnvironment.VertexInfos[Index(x + i, y + j)].Position;
                    }
                    else
                    {
                        valid[index] = false;
                        position[index] = new Vector3(0, 0, 0);
                    }
                }
            // Triangles
            // 0 - 1 - 4
            // 0 - 4 - 3
            // 1 - 5 - 4
            // 3 - 4 - 7
            // 4 - 5 - 8
            // 4 - 8 - 7
            // get cross values
            int[,] indices = new int[6, 3] { { 0, 3, 4 }, { 0, 4, 1 }, { 1, 4, 5 }, { 3, 7, 4 }, { 4, 7, 8 }, { 4, 8, 5 } };
            terrainVertex.Normal = new Vector3(0, 0, 0);
            for (int i = 0; i < 6; i++)
            {
                // pass the invalid situation
                for (int j = 0; j < 3; j++)
                    if (!valid[indices[i, j]])
                        continue;
                // put point 4 at the middle of array
                while (indices[i, 1] != 4)
                {
                    int t = indices[i, 0]; indices[i, 0] = indices[i, 1]; indices[i, 1] = indices[i, 2]; indices[i, 2] = t;
                }
                // get edge 1 and edge 2, then cross it
                terrainVertex.Normal += Vector3.Cross(position[indices[i, 1]] - position[indices[i, 0]], position[indices[i, 1]] - position[indices[i, 2]]);
            }

            // generate tanget
            if (valid[5])
                terrainVertex.Tanget = position[5] - position[4];
            else
                terrainVertex.Tanget = position[4] - position[3];
            terrainVertex.Tanget.Normalize();

            // generate binormal
            terrainVertex.Binormal = Vector3.Cross(terrainVertex.Tanget, terrainVertex.Normal);

            return terrainVertex;
        }

        public void Draw(Matrix world, Matrix view, Matrix projection)
        {
            //foreach (LandscapePatchRenderer cellRenderer in _LandscapePatchRenderers)
            //{
            //    cellRenderer.Draw(world, view, projection);
            //}
        }
    }

    public class InvalidTerrainException : Exception
    {
        public InvalidTerrainException() : base() { }
        public InvalidTerrainException(string message) : base(message) { }
        public InvalidTerrainException(string message, Exception innerException) : base(message, innerException) { }
    }
}
